package temp;

/********************************************************************************
* BankClienAgent:
* ---------------
* This class is an example showing how to use the classes SequentialBehaviour,
* OneShotBehaviour and CyclicBehaviourvto program an agent.
*
* Version 1.0 - July 2003
* Author: Ambroise Ncho, under the supervision of Professeur Jean Vaucher
*
* Universite de Montreal - DIRO
*
*********************************************************************************/

import jade.core.*;
import jade.core.behaviours.*;
import jade.domain.*;
import jade.domain.FIPAAgentManagement.*;
import jade.lang.acl.*;
import jade.util.leap.*;

public class BankServerAgent extends Agent implements BankVocabulary {
// -------------------------------------------------------------------

   private int idCnt = 0;
   private Map accounts = new HashMap();
   private Map operations = new HashMap();

   protected void setup() {
// ------------------------

      // Set this agent main behaviour
      SequentialBehaviour sb = new SequentialBehaviour();
      sb.addSubBehaviour(new RegisterInDF(this));
      sb.addSubBehaviour(new ReceiveMessages(this));
      addBehaviour(sb);
   }

   class RegisterInDF extends OneShotBehaviour {
// ---------------------------------------------  Register in the DF for the client agent
//                                                be able to retrieve its AID
      RegisterInDF(Agent a) {
         super(a);
      }

      public void action() {

         ServiceDescription sd = new ServiceDescription();
         sd.setType(SERVER_AGENT);
         sd.setName(getName());
         sd.setOwnership("Prof6802");
         DFAgentDescription dfd = new DFAgentDescription();
         dfd.setName(getAID());
         dfd.addServices(sd);
         try {
            DFAgentDescription[] dfds = DFService.search(myAgent, dfd);
            if (dfds.length > 0 ) {
               DFService.deregister(myAgent, dfd);
            }
            DFService.register(myAgent, dfd);
            System.out.println(getLocalName() + " is ready.");
         }
         catch (Exception ex) {
            System.out.println("Failed registering with DF! Shutting down...");
            ex.printStackTrace();
            doDelete();
         }
      }
   }

   class ReceiveMessages extends CyclicBehaviour {
// -----------------------------------------------  Receive requests and queries from client
//                                                  agent and launch appropriate handlers

      public ReceiveMessages(Agent a) {

         super(a);
      }

      public void action() {

         ACLMessage msg = receive();
         if (msg == null) { block(); return; }
         try {
            Object content = msg.getContentObject();

            switch (msg.getPerformative()) {

               case (ACLMessage.REQUEST):

                  System.out.println("Request from " + msg.getSender().getLocalName());

                  if (content instanceof CreateAccount)
                     addBehaviour(new HandleCreateAccount(myAgent, msg));
                  else if (content instanceof MakeOperation)
                     addBehaviour(new HandleOperation(myAgent, msg));
                  else
                     replyNotUnderstood(msg);
                  break;

               case (ACLMessage.QUERY_REF):

                  System.out.println("Query from " + msg.getSender().getLocalName());

                  if (content instanceof Information)
                     addBehaviour(new HandleInformation(myAgent, msg));
                  else
                     replyNotUnderstood(msg);
                  break;

               default: replyNotUnderstood(msg);
            }
         }
         catch(Exception ex) { ex.printStackTrace(); }
      }
   }

   class HandleCreateAccount extends OneShotBehaviour {
// ----------------------------------------------------  Handler for a CreateAccount request

      private ACLMessage request;

      HandleCreateAccount(Agent a, ACLMessage request) {

         super(a);
         this.request = request;
      }

      public void action() {

         try {
            CreateAccount ca = (CreateAccount) request.getContentObject();
            Account acc = new Account();
            String id = generateId();
            acc.setId(id);
            acc.setName(ca.getName());
            ACLMessage reply = request.createReply();
            reply.setPerformative(ACLMessage.INFORM);
            reply.setContentObject(acc);
            send(reply);
            accounts.put(id, acc);
            operations.put(id, new ArrayList());
            System.out.println("Account created!");
         }
         catch(Exception ex) { ex.printStackTrace(); }
      }
   }

   class HandleOperation extends OneShotBehaviour {
// ------------------------------------------------  Handler for an Operation request

      private ACLMessage request;

      HandleOperation(Agent a, ACLMessage request) {

         super(a);
         this.request = request;
      }

      public void action() {

         try {
            MakeOperation mo = (MakeOperation) request.getContentObject();
            ACLMessage reply = request.createReply();
            Object obj = processOperation(mo);
            if (obj == null) replyNotUnderstood(request);
            else {
               reply.setPerformative(ACLMessage.INFORM);
               reply.setContentObject((java.io.Serializable)obj);
            }
            send(reply);
            System.out.println("Operation processed.");
         }
         catch(Exception ex) { ex.printStackTrace(); }
      }
   }

   class HandleInformation extends OneShotBehaviour {
// --------------------------------------------------  Handler for an Information query

      private ACLMessage query;

      HandleInformation(Agent a, ACLMessage query) {

         super(a);
         this.query = query;
      }

      public void action() {

         try {
            Information info = (Information) query.getContentObject();
            Object obj = processInformation(info);
            if (obj == null) replyNotUnderstood(query);
            else {
               ACLMessage reply = query.createReply();
               reply.setPerformative(ACLMessage.INFORM);
               reply.setContentObject((java.io.Serializable)obj);
               send(reply);
            }

            System.out.println("Information processed.");
         }
         catch(Exception ex) { ex.printStackTrace(); }
      }
   }

   void replyNotUnderstood(ACLMessage msg) {
// -----------------------------------------
      try {
         java.io.Serializable content = msg.getContentObject();
         ACLMessage reply = msg.createReply();
         reply.setPerformative(ACLMessage.NOT_UNDERSTOOD);
         reply.setContentObject(content);
         send(reply);
      }
      catch(Exception ex) { ex.printStackTrace(); }
   }

   Object processOperation(MakeOperation mo) {
// --------------------------------------------

      Account acc = (Account)accounts.get(mo.getAccountId());
      if (acc == null) return newProblem(ACCOUNT_NOT_FOUND);
      if (mo.getAmount() <= 0) return newProblem(ILLEGAL_OPERATION);

      if (mo.getType() != DEPOSIT && mo.getType() != WITHDRAWAL)
         return null;
      if (mo.getType() == DEPOSIT)
         acc.setBalance(acc.getBalance() + mo.getAmount());
      else if (mo.getType() == WITHDRAWAL) {
         if (mo.getAmount() > acc.getBalance())
            return newProblem(NOT_ENOUGH_MONEY);
         acc.setBalance(acc.getBalance() - mo.getAmount());
      }
      Operation op = new Operation();  // <-- register the operation
      op.setAccountId(acc.getId());
      op.setType(mo.getType());
      op.setAmount(mo.getAmount());
      op.setBalance(acc.getBalance());
      op.setDate(new java.util.Date());
      List l = (List)operations.get(acc.getId());
      l.add(op);
      operations.put(acc.getId(), l);
      return acc;
   }

   Object processInformation(Information info) {
// ---------------------------------------------

      Account acc = (Account)accounts.get(info.getAccountId());
      if (acc == null) return newProblem(ACCOUNT_NOT_FOUND);

      java.util.Date date = new java.util.Date();
      Operation op = new Operation();              // <-- Apply admin charge
      op.setType(ADMIN);
      op.setAmount(info.getType()==BALANCE ? BAL_CHARGE : OPER_CHARGE);
      acc.setBalance(acc.getBalance() - op.getAmount());
      op.setBalance(acc.getBalance());
      op.setAccountId(acc.getId());
      op.setDate(date);
      List l = (List)operations.get(acc.getId());
      l.add(op);
      operations.put(acc.getId(), l);

      if (info.getType() == BALANCE) return acc;

      if (info.getType() == OPERATIONS) {
         OperationList opList = new OperationList();
         opList.setAccountId(info.getAccountId());
         opList.setOperations((List)operations.get(info.getAccountId()));
         return opList;
      }
      return null;
   }


//--------------------------- Utility methods ----------------------------//

   Problem newProblem(int num) {
// -----------------------------

      String msg = "";

      if (num == ACCOUNT_NOT_FOUND)
         msg = PB_ACCOUNT_NOT_FOUND;

      else if (num == NOT_ENOUGH_MONEY)
         msg = PB_NOT_ENOUGH_MONEY;

      else if (num == ILLEGAL_OPERATION)
         msg = PB_ILLEGAL_OPERATION;

      Problem prob = new Problem();
      prob.setNum(num);
      prob.setMsg(msg);
      return prob;
   }

   public String generateId() {
// ----------------------------

      return hashCode() + "" + (idCnt++);
   }
}


//=========================== External classes ===========================//

class Account implements java.io.Serializable {
// --------------------------------------------

   private String id;
   private String name;
   private float balance = 0;

   public String getId() {
      return id;
   }

   public String getName() {
      return name;
   }

   public float getBalance() {
      return balance;
   }

   public void setId(String id) {
      this.id = id;
   }

   public void setName(String name) {
      this.name = name;
   }

   public void setBalance(float balance) {
      this.balance = balance;
   }

   public String toString() {
      return name + "  # " + id + "  --> balance = " + balance;
   }
}

class CreateAccount implements java.io.Serializable {
// --------------------------------------------------

   private String name;

   public String getName() {
     return name;
   }

   public void setName(String name) {
      this.name = name;
   }
}

class Operation implements BankVocabulary, java.io.Serializable {
// -------------------------------------------------------------

   private int type;
   private float amount;
   private float balance;
   private String accountId;
   private java.util.Date date = null;

   public int getType() {
      return type;
   }

   public float getAmount() {
     return amount;
   }

   public float getBalance() {
     return balance;
   }

   public String getAccountId() {
      return accountId;
   }

   public java.util.Date getDate() {
     return date;
   }

   public void setType(int type) {
      this.type = type;
   }

   public void setAmount(float amount) {
      this.amount = amount;
   }

   public void setBalance(float balance) {
      this.balance = balance;
   }

   public void setAccountId(String accountId) {
      this.accountId = accountId;
   }

   public void setDate(java.util.Date date) {
      this.date = date;
   }

   public String getName() {
      if (type == DEPOSIT) return "Depos.";
      if (type == WITHDRAWAL) return "Withd.";
      return "Admin.";
   }

   public String toString() {
      return "\n\t" + date + "  " + getName() +
             "  " + amount + "  " + balance;
   }
}

class MakeOperation implements java.io.Serializable {
// --------------------------------------------------

   private String accountId;
   private int type;
   private float amount;

   public String getAccountId() {
      return accountId;
   }

   public int getType() {
      return type;
   }

   public float getAmount() {
     return amount;
   }

   public void setAccountId(String accountId) {
      this.accountId = accountId;
   }

   public void setType(int type) {
      this.type = type;
   }

   public void setAmount(float amount) {
      this.amount = amount;
   }
}

class OperationList implements java.io.Serializable {
// --------------------------------------------------

   private String accountId;
   private List operations;

   public String getAccountId() {
     return accountId;
   }

   public List getOperations() {
     return operations;
   }

   public void setAccountId(String accountId) {
      this.accountId = accountId;
   }

   public void setOperations(List operations) {
      this.operations = operations;
   }

   public String toString() {
      String s = "\n\tLIST OF OPERATIONS:";
      for (Iterator it = operations.iterator(); it.hasNext();) {
         Operation op = (Operation)it.next();
         s += op.toString();
      }
      return s;
   }
}

class Information implements java.io.Serializable {
// ------------------------------------------------

   private int type;
   private String accountId;

   public int getType() {
      return type;
   }

   public String getAccountId() {
      return accountId;
   }

   public void setType(int type) {
      this.type = type;
   }

   public void setAccountId(String accountId) {
      this.accountId = accountId;
   }
}

class Problem implements java.io.Serializable {
// --------------------------------------------

   private int num;
   private String msg;

   public int getNum() {
      return this.num;
   }

   public String getMsg() {
      return this.msg;
   }

   public void setNum(int num) {
      this.num = num;
   }

   public void setMsg(String msg) {
      this.msg = msg;
   }
}